package optimizers.ganeat.nand;

import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.swing.JPanel;

import optimizers.commons.GAConstants;
import optimizers.commons.RunConfig;
import optimizers.ganeat.Connection;
import optimizers.ganeat.Constants;
import optimizers.ganeat.Individual;
import optimizers.ganeat.Node;

import util.MyUtil;
import visualization.PlotExpData;

import model.InhibitingSequence;
import model.Input;
import model.InvalidConcentrationException;
import model.InvalidKException;
import model.OligoSystemComplex;
import model.OligoSystemGeneral;
import model.Sequence;
import model.SimpleSequence;
import model.Template;

import fitness.BistableResult;
import fitness.FitnessResult;
import fitness.NANDResult;

public class IndividualNAND extends Individual {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	protected double[] sequenceConcentration = new double[2];

	public IndividualNAND(Map<String, Integer> innovations,
			Map<String, String> nodeNames, int geneIndex, int indivIndex, RunConfig config) {
		super(innovations, nodeNames, geneIndex, indivIndex, config);
		// TODO Auto-generated constructor stub
	}

	
	public void evaluateFitness() {
		Random rand = new Random();
		NANDResult result = new NANDResult();
		result.test1 = rand.nextDouble();
		result.test2 = rand.nextDouble();
		result.test3 = rand.nextDouble();
		result.test4 = rand.nextDouble();
		this.fitnessResult = result;
		/*
		 * FitnessResultNAND result = new FitnessResultNAND();
		 * sequenceConcentration = new double[] { ConstantsNAND.conc_low,
		 * ConstantsNAND.conc_low }; OligoSystemComplex OligoModelComplex =
		 * getOligoModel(); Input input1 = new Input(0,0,Integer.MAX_VALUE);
		 * Input input2 = new Input(0,0,Integer.MAX_VALUE); Input input3 = new
		 * Input(0,0,Integer.MAX_VALUE); Input[] inputs = new Input[] {input1,
		 * input2, input3 }; OligoModelComplex.setInput(inputs); double[][]
		 * timeSeries = OligoModelComplex.calculateTimeSeries(); if
		 * (input1.timeStep >= timeSeries[0].length) { result.test1 =
		 * ConstantsNAND.conc_low; } else { result.test1 =
		 * timeSeries[2][input1.timeStep]; }
		 * 
		 * sequenceConcentration = new double[] { ConstantsNAND.conc_low,
		 * ConstantsNAND.conc_high }; OligoModelComplex = getOligoModel();
		 * OligoModelComplex.setInput(inputs); timeSeries =
		 * OligoModelComplex.calculateTimeSeries(); if (input1.timeStep >=
		 * timeSeries[0].length) { result.test2 = ConstantsNAND.conc_low; } else
		 * { result.test2 = timeSeries[2][input1.timeStep]; }
		 * 
		 * sequenceConcentration = new double[] { ConstantsNAND.conc_high,
		 * ConstantsNAND.conc_low }; OligoModelComplex = getOligoModel();
		 * OligoModelComplex.setInput(inputs); timeSeries =
		 * OligoModelComplex.calculateTimeSeries(); if (input1.timeStep >=
		 * timeSeries[0].length) { result.test3 = ConstantsNAND.conc_low; } else
		 * { result.test3 = timeSeries[2][input1.timeStep]; }
		 * sequenceConcentration = new double[] { ConstantsNAND.conc_high,
		 * ConstantsNAND.conc_high }; OligoModelComplex = getOligoModel();
		 * OligoModelComplex.setInput(inputs); timeSeries =
		 * OligoModelComplex.calculateTimeSeries();;
		 * 
		 * if (input1.timeStep >= timeSeries[0].length) { result.test1 =
		 * ConstantsNAND.conc_high; } else { result.test1 =
		 * timeSeries[2][input1.timeStep]; } this.fitnessResult = result; return
		 * result;
		 */
	}

	@Override
	public JPanel displayTimeSereis() {
		JPanel result = new JPanel();
		sequenceConcentration = new double[] { ConstantsNAND.conc_low,
				ConstantsNAND.conc_low };
		OligoSystemGeneral OligoModel = getOligoModel();
		double[][] data1 = OligoModel.getTimeSeriesComplexModel();
		String[] seqNames = new String[data1.length];
		for (int i = 0; i < data1.length; i++) {
			seqNames[i] = OligoModel.intToName.get(i);
		}

		sequenceConcentration = new double[] { ConstantsNAND.conc_high,
				ConstantsNAND.conc_low };
		OligoModel = getOligoModel();
		double[][] data2 = OligoModel.getTimeSeriesComplexModel();

		sequenceConcentration = new double[] { ConstantsNAND.conc_low,
				ConstantsNAND.conc_high };
		OligoModel = getOligoModel();
		double[][] data3 = OligoModel.getTimeSeriesComplexModel();

		sequenceConcentration = new double[] { ConstantsNAND.conc_high,
				ConstantsNAND.conc_high };
		OligoModel = getOligoModel();
		double[][] data4 = OligoModel.getTimeSeriesComplexModel();

		final PlotExpData demo1 = new PlotExpData("Generation "
				+ this.generationIndex + " Index " + this.individualIndex,
				data1, seqNames, "Time", "Expression");
		final PlotExpData demo2 = new PlotExpData("Generation "
				+ this.generationIndex + " Index " + this.individualIndex,
				data2, seqNames, "Time", "Expression");
		final PlotExpData demo3 = new PlotExpData("Generation "
				+ this.generationIndex + " Index " + this.individualIndex,
				data3, seqNames, "Time", "Expression");
		final PlotExpData demo4 = new PlotExpData("Generation "
				+ this.generationIndex + " Index " + this.individualIndex,
				data4, seqNames, "Time", "Expression");
		GridLayout grid = new GridLayout(1, 1);
		result.setLayout(grid);
		grid.setColumns(1);
		// result.add(demo1);
		// result.add(demo2);
		// result.add(demo3);
		result.add(demo4);
		return (result);
	}

	
	public double[][] getSequenceConcentration(int nSimpleSequences) {
		double[][] seqConcentration = new double[nSimpleSequences + 1][nSimpleSequences];
		for (int i = 0; i < nSimpleSequences; ++i) {
			seqConcentration[0][i] = ConstantsNAND.conc_low;
		}
		seqConcentration[0][0] = this.sequenceConcentration[0];
		seqConcentration[0][1] = this.sequenceConcentration[1];
		return seqConcentration;
	}

	@Override
	public OligoSystemGeneral getOligoModel() {
		Map<String, Integer> nameToInt = new HashMap<String, Integer>();
		Map<Integer, String> intToName = new HashMap<Integer, String>();
		int nSimpleSequences = 0;
		int nSequences = 0;
		for (Node node : this.nodeGenes) {
			if (node.type == node.SIMPLE_SEQUENCE) {
				nameToInt.put(node.name, nSimpleSequences);
				intToName.put(nSimpleSequences, node.name);
				nSimpleSequences++;
			}
		}
		nSequences = nSimpleSequences;
		for (Node node : this.nodeGenes) {
			if (node.type == node.INHIBITING_SEQUENCE) {
				nameToInt.put(node.name, nSequences);
				intToName.put(nSequences, node.name);
				nSequences++;
			}
		}
		double[][][] template = new double[nSimpleSequences + 1][nSimpleSequences][nSimpleSequences];
		double[][] seqK = new double[nSimpleSequences + 1][nSimpleSequences];
		double[][] inhK = new double[nSimpleSequences + 1][nSimpleSequences];

		int index;

		// simple sequences K
		for (Node node : this.nodeGenes) {
			if (node.type == node.SIMPLE_SEQUENCE) {
				seqK[0][nameToInt.get(node.name)] = node.parameter;
			}
		}

		// inhibiting sequences K
		for (Node node : this.nodeGenes) {
			if (node.type == node.INHIBITING_SEQUENCE) {
				for (Node tmp1 : this.nodeGenes) {
					for (Node tmp2 : this.nodeGenes) {
						if (node.name.equals("I" + tmp1.name + tmp2.name)) {
							int index1 = nameToInt.get(tmp1.name);
							int index2 = nameToInt.get(tmp2.name);
							inhK[index1 + 1][index2] = MyUtil.calculateInhK(
									seqK[0][index1], seqK[0][index2]);
							seqK[index1 + 1][index2] = inhK[index1 + 1][index2] / 3;
						}
					}
				}
			}
		}

		// Activating Template Concentration
		for (Connection connection : this.connectionGenes) {
			if (connection.from.type == Node.SIMPLE_SEQUENCE
					&& connection.to.type == Node.SIMPLE_SEQUENCE
					&& connection.enabled) {
				int index1 = nameToInt.get(connection.from.name);
				int index2 = nameToInt.get(connection.to.name);
				template[0][index1][index2] = connection.parameter;
			}
		}

		// Inhibiting Template Concentration
		for (Connection connection : this.connectionGenes) {
			if (connection.from.type == Node.SIMPLE_SEQUENCE
					&& connection.to.type == Node.INHIBITING_SEQUENCE
					&& connection.enabled) {
				for (Node tmp1 : this.nodeGenes) {
					for (Node tmp2 : this.nodeGenes) {
						if (connection.to.name.equals("I" + tmp1.name
								+ tmp2.name)) {
							index = nameToInt.get(connection.from.name);
							int index1 = nameToInt.get(tmp1.name);
							int index2 = nameToInt.get(tmp2.name);
							template[index + 1][index1][index2] = connection.parameter;
						}
					}
				}
			}
		}

		double[][] seqConcentration = getSequenceConcentration(nSimpleSequences);
		ArrayList<ArrayList<Sequence>> sequences = new ArrayList<ArrayList<Sequence>>(
				nSimpleSequences + 1);
		sequences.add(new ArrayList<Sequence>(nSimpleSequences));
		int total = 0;
		for (int i = 0; i < nSimpleSequences; ++i) {
			try {
				if (i == nameToInt.get("i1") || i == nameToInt.get("i2")) {
					sequences.get(0).add(
							new SimpleSequence(i, seqConcentration[0][i],
									seqK[0][i], true));
				} else {
					sequences.get(0).add(
							new SimpleSequence(i, seqConcentration[0][i],
									seqK[0][i]));
				}
				total++;
			} catch (InvalidKException e) {
				e.printStackTrace();
			} catch (InvalidConcentrationException e) {
				e.printStackTrace();
			}
		}
		int inhTotal = 0;
		for (int i = 1; i <= nSimpleSequences; ++i) {
			sequences.add(new ArrayList<Sequence>(nSimpleSequences));
			for (int j = 0; j < nSimpleSequences; ++j) {
				if (seqK[i][j] == 0) {
					sequences.get(i).add(null);
				} else {
					try {
						sequences.get(i).add(
								new InhibitingSequence(seqConcentration[i][j],
										seqK[i][j], inhK[i][j],
										(SimpleSequence) sequences.get(0).get(
												i - 1),
										(SimpleSequence) sequences.get(0)
												.get(j)));
						inhTotal++;
					} catch (InvalidKException e) {
						e.printStackTrace();
					} catch (InvalidConcentrationException e) {
						e.printStackTrace();
					}
				}
			}
		}

		ArrayList<ArrayList<ArrayList<Template>>> templates = new ArrayList<ArrayList<ArrayList<Template>>>(
				nSimpleSequences + 1);
		templates.add(new ArrayList<ArrayList<Template>>(nSimpleSequences));
		for (int i = 0; i < nSimpleSequences; ++i) {
			templates.get(0).add(new ArrayList<Template>(nSimpleSequences));
			for (int j = 0; j < nSimpleSequences; ++j) {
				if (template[0][i][j] != 0) {
					templates.get(0).get(i).add(
							new Template(template[0][i][j],
									(SimpleSequence) sequences.get(0).get(i),
									sequences.get(0).get(j),
									(InhibitingSequence) sequences.get(i + 1)
											.get(j)));
				} else {
					templates.get(0).get(i).add(null);
				}
			}
		}
		for (int i = 1; i <= nSimpleSequences; ++i) {
			templates.add(new ArrayList<ArrayList<Template>>(nSimpleSequences));
			for (int j = 0; j < nSimpleSequences; ++j) {
				templates.get(i).add(new ArrayList<Template>(nSimpleSequences));
				for (int k = 0; k < nSimpleSequences; ++k) {
					if (template[i][j][k] != 0) {
						templates.get(i).get(j).add(
								new Template(template[i][j][k],
										(SimpleSequence) sequences.get(0).get(
												i - 1), sequences.get(j + 1)
												.get(k), null));
					} else {
						templates.get(i).get(j).add(null);
					}
				}
			}
		}

		OligoSystemGeneral oligoSystem = new OligoSystemGeneral(nSimpleSequences,template,
				seqK,inhK, seqConcentration, model.Constants.exo, intToName, nameToInt);
		return oligoSystem;
	}
}
